#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
pytest 插件
用于执行 Python 单元测试
"""

import os
import subprocess
import json
import xml.etree.ElementTree as ET
from typing import Dict, List, Any


class PytestPlugin:
    """pytest 插件"""
    
    def __init__(self):
        self.name = "pytest"
        self.description = "Python 单元测试执行"
    
    def execute(self, config: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]:
        """执行 pytest 测试"""
        try:
            # 检查是否有测试文件
            test_files = self._find_test_files(config.get('test_paths', ['tests', 'test']))
            if not test_files and not config.get('force_run', False):
                return {
                    'success': True,
                    'output': '未找到测试文件，跳过 pytest 执行',
                    'exit_code': 0
                }
            
            # 检查 pytest 是否安装
            if not self._check_pytest_installed():
                return {
                    'success': False,
                    'output': 'pytest 未安装，请运行: pip install pytest',
                    'exit_code': 1
                }
            
            # 获取配置参数
            test_paths = config.get('test_paths', ['tests'])
            markers = config.get('markers', [])
            coverage = config.get('coverage', False)
            coverage_min = config.get('coverage_min', 80)
            verbose = config.get('verbose', True)
            fail_fast = config.get('fail_fast', False)
            parallel = config.get('parallel', False)
            max_workers = config.get('max_workers', 'auto')
            
            # 构建命令
            cmd = ['pytest']
            
            # 添加详细输出
            if verbose:
                cmd.append('-v')
            
            # 添加快速失败
            if fail_fast:
                cmd.append('-x')
            
            # 添加标记过滤
            if markers:
                for marker in markers:
                    cmd.extend(['-m', marker])
            
            # 添加覆盖率检查
            if coverage:
                cmd.extend(['--cov=.', '--cov-report=term-missing'])
                if coverage_min:
                    cmd.extend([f'--cov-fail-under={coverage_min}'])
            
            # 添加并行执行
            if parallel:
                if self._check_pytest_xdist_installed():
                    cmd.extend(['-n', str(max_workers)])
                else:
                    print("警告: pytest-xdist 未安装，无法并行执行测试")
            
            # 添加 JUnit XML 输出
            junit_file = 'pytest-results.xml'
            cmd.extend(['--junit-xml', junit_file])
            
            # 添加测试路径
            for path in test_paths:
                if os.path.exists(path):
                    cmd.append(path)
            
            # 如果没有有效路径，使用当前目录
            if len([p for p in test_paths if os.path.exists(p)]) == 0:
                cmd.append('.')
            
            # 执行命令
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                timeout=600
            )
            
            # 解析 JUnit XML 结果
            test_results = self._parse_junit_xml(junit_file)
            
            # 清理临时文件
            if os.path.exists(junit_file):
                os.remove(junit_file)
            
            # 解析输出
            output_lines = result.stdout.split('\n') if result.stdout else []
            error_lines = result.stderr.split('\n') if result.stderr else []
            
            # 提取测试统计信息
            stats = self._extract_test_stats(output_lines)
            
            # 提取覆盖率信息
            coverage_info = None
            if coverage:
                coverage_info = self._extract_coverage_info(output_lines)
            
            if result.returncode == 0:
                output = "pytest 测试全部通过！\n\n"
                output += f"测试统计:\n"
                output += f"- 总计: {stats['total']}\n"
                output += f"- 通过: {stats['passed']}\n"
                output += f"- 跳过: {stats['skipped']}\n"
                
                if coverage_info:
                    output += f"\n覆盖率: {coverage_info['percentage']}%\n"
                    if coverage_info['missing_lines']:
                        output += f"未覆盖行数: {coverage_info['missing_lines']}\n"
                
                return {
                    'success': True,
                    'output': output,
                    'exit_code': 0,
                    'details': {
                        'test_stats': stats,
                        'coverage': coverage_info,
                        'test_results': test_results
                    }
                }
            else:
                output = f"pytest 测试失败！\n\n"
                output += f"测试统计:\n"
                output += f"- 总计: {stats['total']}\n"
                output += f"- 通过: {stats['passed']}\n"
                output += f"- 失败: {stats['failed']}\n"
                output += f"- 错误: {stats['errors']}\n"
                output += f"- 跳过: {stats['skipped']}\n"
                
                if coverage_info:
                    output += f"\n覆盖率: {coverage_info['percentage']}%\n"
                
                # 显示失败的测试
                failed_tests = [t for t in test_results if t['status'] == 'failed']
                if failed_tests:
                    output += "\n失败的测试:\n"
                    for test in failed_tests[:5]:  # 只显示前5个
                        output += f"- {test['name']}: {test['message']}\n"
                    if len(failed_tests) > 5:
                        output += f"... 还有 {len(failed_tests) - 5} 个失败测试\n"
                
                return {
                    'success': False,
                    'output': output,
                    'exit_code': result.returncode,
                    'details': {
                        'test_stats': stats,
                        'coverage': coverage_info,
                        'test_results': test_results,
                        'failed_tests': failed_tests
                    }
                }
                
        except subprocess.TimeoutExpired:
            return {
                'success': False,
                'output': 'pytest 执行超时',
                'exit_code': 124
            }
        except Exception as e:
            return {
                'success': False,
                'output': f'pytest 执行失败: {str(e)}',
                'exit_code': 1
            }
    
    def _find_test_files(self, paths: List[str]) -> List[str]:
        """查找测试文件"""
        test_files = []
        
        for path in paths:
            if os.path.isfile(path) and (path.startswith('test_') or path.endswith('_test.py')):
                test_files.append(path)
            elif os.path.isdir(path):
                for root, dirs, files in os.walk(path):
                    # 跳过常见的忽略目录
                    dirs[:] = [d for d in dirs if d not in ['.git', '__pycache__', '.pytest_cache']]
                    
                    for file in files:
                        if file.startswith('test_') or file.endswith('_test.py'):
                            test_files.append(os.path.join(root, file))
        
        return test_files
    
    def _check_pytest_installed(self) -> bool:
        """检查 pytest 是否安装"""
        try:
            subprocess.run(['pytest', '--version'], capture_output=True, check=True)
            return True
        except (subprocess.CalledProcessError, FileNotFoundError):
            return False
    
    def _check_pytest_xdist_installed(self) -> bool:
        """检查 pytest-xdist 是否安装"""
        try:
            import xdist
            return True
        except ImportError:
            return False
    
    def _parse_junit_xml(self, xml_file: str) -> List[Dict[str, Any]]:
        """解析 JUnit XML 结果"""
        if not os.path.exists(xml_file):
            return []
        
        try:
            tree = ET.parse(xml_file)
            root = tree.getroot()
            
            results = []
            for testcase in root.findall('.//testcase'):
                test_result = {
                    'name': testcase.get('name'),
                    'classname': testcase.get('classname'),
                    'time': float(testcase.get('time', 0)),
                    'status': 'passed'
                }
                
                # 检查失败
                failure = testcase.find('failure')
                if failure is not None:
                    test_result['status'] = 'failed'
                    test_result['message'] = failure.get('message', '')
                    test_result['details'] = failure.text or ''
                
                # 检查错误
                error = testcase.find('error')
                if error is not None:
                    test_result['status'] = 'error'
                    test_result['message'] = error.get('message', '')
                    test_result['details'] = error.text or ''
                
                # 检查跳过
                skipped = testcase.find('skipped')
                if skipped is not None:
                    test_result['status'] = 'skipped'
                    test_result['message'] = skipped.get('message', '')
                
                results.append(test_result)
            
            return results
        except Exception:
            return []
    
    def _extract_test_stats(self, output_lines: List[str]) -> Dict[str, int]:
        """提取测试统计信息"""
        stats = {
            'total': 0,
            'passed': 0,
            'failed': 0,
            'errors': 0,
            'skipped': 0
        }
        
        for line in output_lines:
            if 'passed' in line and 'failed' in line:
                # 解析类似 "5 passed, 2 failed, 1 skipped" 的行
                parts = line.split(',')
                for part in parts:
                    part = part.strip()
                    if 'passed' in part:
                        stats['passed'] = int(part.split()[0])
                    elif 'failed' in part:
                        stats['failed'] = int(part.split()[0])
                    elif 'error' in part:
                        stats['errors'] = int(part.split()[0])
                    elif 'skipped' in part:
                        stats['skipped'] = int(part.split()[0])
                
                stats['total'] = stats['passed'] + stats['failed'] + stats['errors'] + stats['skipped']
                break
        
        return stats
    
    def _extract_coverage_info(self, output_lines: List[str]) -> Dict[str, Any]:
        """提取覆盖率信息"""
        coverage_info = {
            'percentage': 0,
            'missing_lines': 0,
            'covered_lines': 0,
            'total_lines': 0
        }
        
        for line in output_lines:
            if 'TOTAL' in line and '%' in line:
                # 解析类似 "TOTAL    100    20    80%" 的行
                parts = line.split()
                if len(parts) >= 4:
                    try:
                        coverage_info['percentage'] = int(parts[-1].replace('%', ''))
                        coverage_info['total_lines'] = int(parts[1])
                        coverage_info['missing_lines'] = int(parts[2])
                        coverage_info['covered_lines'] = coverage_info['total_lines'] - coverage_info['missing_lines']
                    except (ValueError, IndexError):
                        pass
                break
        
        return coverage_info


def execute(config: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]:
    """插件入口函数"""
    plugin = PytestPlugin()
    return plugin.execute(config, context)
